/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "utils/leb128.h"

#include <gmock/gmock.h>
#include <gtest/gtest.h>

namespace panda::leb128::test {

template <class T>
struct TestData {
    T value;
    size_t size;
    uint8_t data[10];
};

// clang-format off

static std::vector<TestData<uint64_t>> unsigned_test_data {
    {0x00,                1, {0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}},
    {0x7f,                1, {0x7f, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}},
    {0xff,                2, {0xff, 0x01, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}},
    {0x2d7f,              2, {0xff, 0x5a, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}},
    {0xffff,              3, {0xff, 0xff, 0x03, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}},
    {0x192d7f,            3, {0xff, 0xda, 0x64, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}},
    {0x1592d7f,           4, {0xff, 0xda, 0xe4, 0x0a, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}},
    {0x11592d7f,          5, {0xff, 0xda, 0xe4, 0x8a, 0x01, 0x80, 0x80, 0x80, 0x80, 0x80}},
    {0xffffffff,          5, {0xff, 0xff, 0xff, 0xff, 0x0f, 0x80, 0x80, 0x80, 0x80, 0x80}},
    {0x1011592d7f,        6, {0xff, 0xda, 0xe4, 0x8a, 0x81, 0x02, 0x80, 0x80, 0x80, 0x80}},
    {0xc1011592d7f,       7, {0xff, 0xda, 0xe4, 0x8a, 0x81, 0x82, 0x03, 0x80, 0x80, 0x80}},
    {0x80c1011592d7f,     8, {0xff, 0xda, 0xe4, 0x8a, 0x81, 0x82, 0x83, 0x04, 0x80, 0x80}},
    {0xffffffffffffffff, 10, {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01}}
};

static std::vector<TestData<uint64_t>> unsigned_partial_decoding_test_data {
    {0xffffffffffffffff, 10, {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03}},
};

static std::vector<TestData<int8_t>> signed_test_data8 {
    {0x00,                      1, {0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}},
    {0x01,                      1, {0x01, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}},
    {-1,                        1, {0x7f, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}},
    {0x40,                      2, {0xc0, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}},
    {static_cast<int8_t>(0x80), 2, {0x80, 0x7f, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}},
    {-0x40,                     1, {0x40, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}}
};

static std::vector<TestData<int16_t>> signed_test_data16 {
    {0x00,                         1, {0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}},
    {0x0102,                       2, {0x82, 0x02, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}},
    {-1,                           1, {0x7f, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}},
    {-0x40,                        1, {0x40, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}},
    {static_cast<int16_t>(0x8000), 3, {0x80, 0x80, 0x7e, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}},
    {static_cast<int16_t>(0x4001), 3, {0x81, 0x80, 0x01, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}}
};

static std::vector<TestData<int32_t>> signed_test_data32 {
    {0x00,                             1, {0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}},
    {0x01020304,                       4, {0x84, 0x86, 0x88, 0x08, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}},
    {-1,                               1, {0x7f, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}},
    {-0x40,                            1, {0x40, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}},
    {static_cast<int32_t>(0x80000000), 5, {0x80, 0x80, 0x80, 0x80, 0x78, 0x80, 0x80, 0x80, 0x80, 0x80}},
    {static_cast<int32_t>(0x40000001), 5, {0x81, 0x80, 0x80, 0x80, 0x04, 0x80, 0x80, 0x80, 0x80, 0x80}}
};

static std::vector<TestData<int64_t>> signed_test_data64 {
    {0x00,                                      1, {0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}},
    {0x40,                                      2, {0xc0, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}},
    {0x7f,                                      2, {0xff, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}},
    {-1,                                        1, {0x7f, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}},
    {static_cast<int64_t>(0x8000000000000000), 10, {0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f}},
    {0x7000000000000001,                       10, {0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xf0, 0x00}},
    {0x100000000000000,                         9, {0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01, 0x00}},
    {-0x40,                                     1, {0x40, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}},
    {-0x1122,                                   2, {0xde, 0x5d, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}}
};

static std::vector<TestData<int8_t>> signed_partial_decoding_test_data8 {
    {1, 10, {0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a}}
};

static std::vector<TestData<int16_t>> signed_partial_decoding_test_data16 {
    {-0x3eff, 10, {0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a}}
};

static std::vector<TestData<int32_t>> signed_partial_decoding_test_data32 {
    {0x5080c101, 10, {0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a}}
};

static std::vector<TestData<int64_t>> signed_partial_decoding_test_data64 {
    {0x9101c305080c101,                        10, {0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a}},
    {static_cast<int64_t>(0x8000000000000000), 10, {0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x5f}}
};

// clang-format on

template <class T>
static void TestDecodeUnsigned(const std::vector<TestData<uint64_t>> &data, bool is_partial = false)
{
    for (auto &t : data) {
        std::ostringstream ss;
        ss << "Test unsigned decoding ";
        ss << std::hex << t.value;
        ss << " with sizeof(T) = ";
        ss << sizeof(T);

        constexpr size_t bitwidth = std::numeric_limits<T>::digits;

        auto [value, size, is_full] = DecodeUnsigned<T>(t.data);
        EXPECT_EQ(is_full, MinimumBitsToStore(t.value) <= bitwidth && !is_partial) << ss.str();
        EXPECT_EQ(size, is_full ? t.size : (bitwidth + 6) / 7) << ss.str();
        EXPECT_EQ(value, static_cast<T>(t.value)) << ss.str();
    }
}

TEST(Leb128, DecodeUnsigned)
{
    TestDecodeUnsigned<uint8_t>(unsigned_test_data);
    TestDecodeUnsigned<uint16_t>(unsigned_test_data);
    TestDecodeUnsigned<uint32_t>(unsigned_test_data);
    TestDecodeUnsigned<uint64_t>(unsigned_test_data);
    TestDecodeUnsigned<uint64_t>(unsigned_partial_decoding_test_data, true);
}

template <class T>
static void TestDecodeSigned(const std::vector<TestData<T>> &data, bool is_partial = false)
{
    for (auto &t : data) {
        std::ostringstream ss;
        ss << "Test signed decoding ";
        ss << std::hex << static_cast<int64_t>(t.value);
        ss << " with sizeof(T) = ";
        ss << sizeof(T);

        constexpr size_t bitwidth = std::numeric_limits<std::make_unsigned_t<T>>::digits;

        auto [value, size, is_full] = DecodeSigned<T>(t.data);
        EXPECT_EQ(is_full, !is_partial) << ss.str();
        EXPECT_EQ(size, is_full ? t.size : (bitwidth + 6) / 7) << ss.str();
        EXPECT_EQ(value, t.value) << ss.str();
    }
}

TEST(Leb128, DecodeSigned)
{
    TestDecodeSigned(signed_test_data8);
    TestDecodeSigned(signed_test_data16);
    TestDecodeSigned(signed_test_data32);
    TestDecodeSigned(signed_test_data64);

    TestDecodeSigned(signed_partial_decoding_test_data8, true);
    TestDecodeSigned(signed_partial_decoding_test_data16, true);
    TestDecodeSigned(signed_partial_decoding_test_data32, true);
    TestDecodeSigned(signed_partial_decoding_test_data64, true);
}

TEST(Leb128, EncodeUnsigned)
{
    for (auto &t : unsigned_test_data) {
        std::ostringstream ss;
        ss << "Test unsigned encoding ";
        ss << std::hex << t.value;

        std::vector<uint8_t> data(t.size);
        size_t n = EncodeUnsigned(t.value, data.data());
        EXPECT_EQ(n, t.size) << ss.str();
        EXPECT_EQ(UnsignedEncodingSize(t.value), t.size) << ss.str();
        EXPECT_THAT(data, ::testing::ElementsAreArray(t.data, t.size)) << ss.str();
    }
}

template <class T>
void TestEncodeSigned(const std::vector<TestData<T>> &data_vec)
{
    for (auto &t : data_vec) {
        std::ostringstream ss;
        ss << "Test signed encoding ";
        ss << std::hex << static_cast<int64_t>(t.value);

        std::vector<uint8_t> data(t.size);
        size_t n = EncodeSigned(t.value, data.data());
        EXPECT_EQ(n, t.size) << ss.str();
        EXPECT_EQ(SignedEncodingSize(t.value), t.size) << ss.str();
        EXPECT_THAT(data, ::testing::ElementsAreArray(t.data, t.size)) << ss.str();
    }
}

TEST(Leb128, EncodeSigned)
{
    TestEncodeSigned(signed_test_data8);
    TestEncodeSigned(signed_test_data16);
    TestEncodeSigned(signed_test_data32);
    TestEncodeSigned(signed_test_data64);
}

}  // namespace panda::leb128::test
